package dev.ece.suit.skybox;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;

import dev.ece.core.cells.model.AbstractModel;
import dev.ece.core.shader.DefaultTextureShader;
import dev.ece.core.sprite.Sprite;
import dev.ece.core.texture.FBOS;
import dev.ece.core.texture.Texture;
import dev.ece.core.vertex.EBO;
import dev.ece.core.vertex.VAO;
import dev.ece.core.vertex.VBO;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.Camera;
import dev.ece.util.tools.Constants;
import dev.ece.util.tools.ploy.TextureMipmapPloy;
import dev.ece.util.tools.transformations.Translation;

public class SkyBall extends Sprite<Object, SkyBallModel> {

	private final Vector3f position = new Vector3f();
	
	private boolean followCamera;
	
	public SkyBall(float radius, String texture_path) {
		this(radius, false, texture_path);
	}
	
	public SkyBall(float radius, Texture texture) {
		this(radius, false, texture);
	}
	
	public SkyBall(float radius, boolean followCamera, String texture_path) {
		super(new SkyBallModel(radius, texture_path));
		this.followCamera = followCamera;
		this.transformations.add(new Translation(position));
	}
	
	public SkyBall(float radius, boolean followCamera, Texture texture) {
		super(new SkyBallModel(radius, texture));
		this.followCamera = followCamera;
		this.transformations.add(new Translation(position));
	}

	@Override
	public void onLogic(Camera camera) {
		if(this.isFollowCamera()) {
			position.setX(camera.position.getX());
			position.setZ(camera.position.getZ());
		}
	}
	
	public Vector3f getPosition() {
		return position;
	}

	public float getRadius() {
		return this.getModel().getRadius();
	}

	public boolean isFollowCamera() {
		return followCamera;
	}

	public void setFollowCamera(boolean followCamera) {
		this.followCamera = followCamera;
	}

}

class SkyBallModel extends AbstractModel<Object> {

	private final Logger log = LogManager.getLogger(getClass());
	
	private String texture_path;
	
	private Texture texture;
	
	private DefaultTextureShader defaultTextureShader;
	
	private float radius;
	
	private List<Vector3f> vertice = new ArrayList<Vector3f>();
	
	private List<Vector2f> uv = new ArrayList<Vector2f>();
	
	private int[][] indice;
	
	private VAO vao;
	
	private VBO verticeVBO;
	
	private VBO uvVBO;
	
	private EBO[] ebos;
	
	public SkyBallModel(float radius, Texture texture) {
		this.radius = radius;
		this.texture = texture;
		this.init();
		this.setup();
	}
	
	public SkyBallModel(float radius, String texture_path) {
		this.radius = radius;
		this.texture_path = texture_path;
		this.init();
		this.setup();
	}
	
	private void init() {
		float angle = 10f;
		int cols = (int) (360f / angle) + 1;
		int rows = (int) (90f / angle) + 1;
		float uvx = 1f / (cols - 1);
		float uvy = 1f / (rows - 1);
		indice = new int[rows - 1][cols + 2];
		Vector3f vertex = null;
		Matrix4f vmatrix = new Matrix4f();
		Matrix4f hmatrix = new Matrix4f();
		for(int row = 0; row<rows; row ++) {
			vmatrix.identity();
			hmatrix.identity();
			vmatrix.rotate(1f, 0, 0, - angle * row);
			for(int col=0; col<cols; col++) {
				hmatrix.rotate(0, 1f, 0, - angle);
				vertex = new Vector3f(0, radius, 0);
				vmatrix.transform(vertex);
				hmatrix.transform(vertex);
				vertice.add(vertex);
				uv.add(new Vector2f(uvx * col, row * uvy));
			}
			if(row < indice.length) {
				indice[row] = new int[cols * 2];
				for(int col=0; col<cols; col++) {
					indice[row][col * 2] = cols * row + col;
					indice[row][col * 2 + 1] = cols * (row + 1) + col;
				}
			}
		}
	}
	
	@Override
	public void onRenderDepthMap(Camera camera, Object value) {}

	@Override
	public void onRenderAlpha(FBOS defer, Camera camera, Object value) {}

	protected void setup() {
		if(texture == null) {
			try {
				texture = Texture.newInstance(
						Constants.getGameFileInputStream(texture_path), 
						TextureMipmapPloy.getInstance());
			} catch (FileNotFoundException e) {
				log.error("未找到天空球纹理", e);
			}
		}
		
		defaultTextureShader = createDefaultTextureShader();
		
		verticeVBO = VBO.newInstance3f(vertice);
		uvVBO = VBO.newInstance2f(uv);
		VBO.unbind();
		
		ebos = new EBO[this.indice.length];
		for(int i=0; i<this.indice.length; i++) {
			ebos[i] = EBO.newInstance(this.indice[i]);
		}
		EBO.unbind();
	}
	
	protected DefaultTextureShader createDefaultTextureShader() {
		return DefaultTextureShader.getDefaultTextureShader();
	}

	@Override
	public void onInitialize() {
		vao = new VAO();
		vao.bind();
		vao.bindVertexArrayBuffer(verticeVBO, 3);
		vao.bindVertexArrayBuffer(uvVBO, 2);
		vao.finish();
		
		verticeVBO = null;
		uvVBO = null;
	}

	@Override
	public void onRender(Camera camera, Object value) {
		defaultTextureShader.glUseProgram();
		defaultTextureShader.glModelViewProjectMatrix();
		defaultTextureShader.glTexture(texture);
		vao.bind();
		for(EBO ebo:ebos) {
			ebo.bind();
			ebo.glDrawElements(GL11.GL_TRIANGLE_STRIP);
		}
	}

	public float getRadius() {
		return radius;
	}
	
	@Override
	public void destroy() {
		if(texture != null) {
			texture.destroy();
		}
		if(vao != null) {
			vao.destroy();
		}
		if(ebos != null) {
			for(EBO ebo:ebos) {
				ebo.destroy();
			}
		}
		vao = null;
		ebos = null;
		texture = null;
		vertice = null;
		uv = null;
	}
	
}
